package com.ludomancy.gamelib.verlet;

import java.awt.Graphics;
import com.ludomancy.gamelib.MathHelper;
import com.ludomancy.gamelib.WorldScreenProjection;

public abstract class Simulation
{
	public float gravity;
	public float airFriction;
	protected VerletObject[] objs = new VerletObject[100];
	protected int objCounter = 0;
	
	public Simulation(float gravity, float airFriction)
	{
		this.gravity = gravity;
		this.airFriction = airFriction;
	}
	
	public void update()
	{
		for (int i = 0; i < objCounter; i++)
		{
			objs[i].update(this);
			for (int j = i+1; j < objCounter; j++)
			{
				handleCollision(objs[i], objs[j]);
			}
		}
	}
	
	public static final int COLLISION_NONE = 0;
	public static final int COLLISION_CIRCLE = 1;
	public static final int COLLISION_MESH = 2;
	protected void handleCollision(VerletObject o1, VerletObject o2)
	{
		if(o1.collisionType == COLLISION_NONE || o2.collisionType == COLLISION_NONE)
			return;
		if(o1.collisionType == COLLISION_MESH && o2.collisionType == COLLISION_MESH)
		{
			Edge[] v1 = o1.getCollisionEdges();
			Edge[] v2 = o2.getCollisionEdges();
			for (int i = 0; i < v1.length; i++)
			{
				Edge e = v1[i];
				for (int j = 0; j < v2.length; j++)
				{
					Edge e2 = v2[j];
					edgeEdgeCollision(e, e2, 0);
				}
			}
		}
		else
		if(o1.collisionType == COLLISION_CIRCLE && o2.collisionType == COLLISION_MESH)
		{
			Vertex v = o1.getCircle();
			Edge[] eedges = o2.getCollisionEdges();
			for (int j = 0; j < eedges.length; j++)
			{
				vertexEdgeCollision(v, eedges[j], 0);
			}
		}
		else
		if(o1.collisionType == COLLISION_MESH && o2.collisionType == COLLISION_CIRCLE)
		{
			Vertex v = o2.getCircle();
			Edge[] eedges = o1.getCollisionEdges();
			for (int j = 0; j < eedges.length; j++)
			{
				vertexEdgeCollision(v, eedges[j], 0);
			}			
		}
	}
	
	protected abstract void paint(Graphics g, WorldScreenProjection proj);
	
	public void addShape(VerletObject obj)
	{
		objs[objCounter++] = obj;
	}
	
	public static boolean edgeEdgeCollision(Edge e1, Edge e2, float friction)
	{
		boolean collided = false;
		collided = vertexEdgeCollision(e1.v1, e2, friction)!=null || collided;
		collided = vertexEdgeCollision(e1.v2, e2, friction)!=null || collided;
		collided = vertexEdgeCollision(e2.v1, e1, friction)!=null || collided;
		collided = vertexEdgeCollision(e2.v2, e1, friction)!=null || collided;
		return collided;
	}
	
	private static float[] vertexEdgeCollisionInfoVec = new float[4];
	public static float[] vertexEdgeCollisionInfo(Vertex v, Edge e)
	{
		float[] distanceInfo = MathHelper.distanceFromPointToEdge(v.px, v.py, e, vertexEdgeCollisionInfoVec);
		float distance = distanceInfo[0];
		float d = v.radii + e.radii - distance;
		if(d > 0.0000001)
		{
			distanceInfo[0] = d;
			return distanceInfo;
		}
		return null;
	}
	
	// 0 - normalX
	// 1 - normalY
	// 2 - interpenetration distance
	// 3 - t of the closest point
	// returns null if no collision
	private static float[] vertexEdgeCollisionNormVec = new float[4];
	public static float[] vertexEdgeCollision(Vertex v, Edge e, float friction)
	{
		float[] distanceInfo = vertexEdgeCollisionInfo(v, e);

		if(distanceInfo != null)
		{
			float distance = distanceInfo[0];
			float t = distanceInfo[1];
			float contactX = distanceInfo[2];
			float contactY = distanceInfo[3];

			// impact normal
			float normX = contactX - v.px;
			float normY = contactY - v.py;
			if(normX == 0 && normY == 0)
			{
				// panic! overimposed!
				return null;
			}
			float[] normalized = MathHelper.normalize(normX, normY, vertexEdgeCollisionNormVec);
			normX = normalized[0]*distance;
			normY = normalized[1]*distance;
			float moveX = normX * 0.95f;
			float moveY = normY * 0.95f;
			float pushX = normX * 0.05f;
			float pushY = normY * 0.05f;
			
			// project both out of the collision
			if(v.fixed)
			{
				e.moveAtPoint(t, moveX, moveY);
				e.pushAtPoint(t, pushX, pushY);
			}
			else
			{
				v.move(-moveX*0.5f, -moveY*0.5f);
				e.moveAtPoint(t, moveX*0.5f, moveY*0.5f);
				v.push(-pushX*0.5f, -pushY*0.5f);
				e.pushAtPoint(t, pushX*0.5f, pushY*0.5f);
			}

			// prepare response
			vertexEdgeCollisionNormVec[0] = normX;
			vertexEdgeCollisionNormVec[1] = normY;
			vertexEdgeCollisionNormVec[2] = distance;
			vertexEdgeCollisionNormVec[3] = t;
			
			// handle friction
//			applyFriction(ret, v, e, friction);
			
			return vertexEdgeCollisionNormVec;
		}
		return null;
	}
	
//	private static float[] applyFrictionProjectVec = new float[2];
//	public static void applyFriction(float[] collisionInfo, Vertex v, Edge e, float friction)
//	{
//		// friction normal
//		float frictionX = collisionInfo[1];
//		float frictionY = -collisionInfo[0];
//		
//		// relative velocity
//		float edgeVel[] = e.getVelocityAtPoint(collisionInfo[3]); 
//		float relVelX = edgeVel[0] - v.velX();
//		float relVelY = edgeVel[1] - v.velY();
//
//		// friction push magnitude
//		float[] tangential = MathHelper.project(relVelX, relVelY, frictionX, frictionY, applyFrictionProjectVec);
//		tangential[0] *= friction;
//		tangential[1] *= friction;
//		
//		// apply frictions
//		e.pushAtPoint(collisionInfo[3], -tangential[0], -tangential[1]);
//	}
}
